#include "native-mmkv.h"
#include "MMKVPredef.h"
#include "MMBuffer.h"
#include "MMKV.h"
#include "MMKVLog.h"
#include "MemoryFile.h"
#include <cstdint>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

#include <assert.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

using namespace std;
using namespace mmkv;

extern int64_t getSizeofSize_t(void);
extern void initializeMMKV_c(char *rootDir, int logLevel, MMKVHandler handler);
extern MMKV *defaultMMKV_c(MMKVMode mode, char *cryptKey);
extern MMKV *mmkvWithID_c(char *mmapID, MMKVMode mode, char *cryptKey, char *rootPath);
extern bool setBool_c(MMKV *kv, char *key, bool value);
extern bool setInt32_t_c(MMKV *kv, char* key, int32_t value);
extern bool setUint32_t_c(MMKV *kv, char* key, uint32_t value);
extern bool setInt64_t_c(MMKV *kv, char* key, int64_t value);
extern bool setUint64_t_c(MMKV *kv, char* key, uint64_t value);
extern bool setFloat_c(MMKV *kv, char* key, float value);
extern bool setDouble_c(MMKV *kv, char* key, double value);
extern bool setString_c(MMKV *kv, char* key, char* value);
extern bool setBytes_c(MMKV *kv, char *key, byteArray *value);
extern bool setStringSet_c(MMKV *kv, char *key, DataList *value);
extern bool getBool_c(MMKV *kv, char *key, bool defaultValue);
extern int32_t getInt32_c(MMKV *kv, char *key, int32_t defaultValue);
extern uint32_t getUInt32_c(MMKV *kv, char *key, uint32_t defaultValue);
extern int64_t getInt64_c(MMKV *kv, char *key, int64_t defaultValue);
extern uint64_t getUInt64_c(MMKV *kv, char *key, uint64_t defaultValue);
extern float getFloat_c(MMKV *kv, char *key, float defaultValue);
extern double getDouble_c(MMKV *kv, char *key, double defaultValue);
extern char* getString_c(MMKV *kv, char *key, char* defaultValue);
extern byteArray *getBytes_c(MMKV *kv, char *key);
extern DataList *getStringSet_c(MMKV *kv, char *key);
extern void removeValueForKey_c(MMKV *kv, char *key);
extern void removeValuesForKeys_c(MMKV *kv, DataList *arrKeys);
extern void sync_c(MMKV *kv, bool sync);
extern void clearMemoryCache_c(MMKV *kv);
extern void close_c(MMKV *kv);
extern bool isFileValid_c(const char *mmapID, char *relatePath);
extern void clearAll_c(MMKV *kv);
extern bool backupOne(char *mmapID, char *dstDir, char *rootPath);
extern bool restoreOne(char *mmapID, char *srcDir, char *rootPath);
extern size_t backupAll(char *dstDir, char *rootPath);
extern size_t restoreAll(char *srcDir, char *rootPath);
extern void setLogLevel_c(MMKVLogLevel level);
extern void setCallbackHandler(MMKVHandler handler);
extern size_t pageSize_c();
extern void trim_c(MMKV *kv);
extern void checkContentChanged_c(MMKV *kv);
extern void lock_c(MMKV *kv);
extern void unlock_c(MMKV *kv);
extern bool tryLock_c(MMKV *kv);
extern void onExit_c();
extern void *CPtrCoercion_c(unsigned long Addr);
extern int32_t writeValueToBuffer_c(MMKV *kv, char* key, void *ptr, int32_t size);
extern size_t getValueSize_c(MMKV *kv, char* key, bool actualSize);
extern bool containsKey_c(MMKV *kv, char* key);
extern size_t count_c(MMKV* kv);
extern size_t totalSize_c(MMKV* kv);
extern size_t actualSize_c(MMKV* kv);
extern DataList *allKeys_c(MMKV* kv);
extern char* mmapID_c(MMKV* kv);
extern char *cryptKey_c(MMKV* kv);
extern bool reKey_c(MMKV *kv, char *cryptKey);
extern void checkReSetCryptKey_c(MMKV *kv, char *cryptKey);

int memcpy_s(void *dest, uint32_t destsz, const void *src, uint32_t count)
{
    memcpy(dest, src, count);
    return 0;
}

int strcpy_s(char *dest, uint32_t destsz, const char *src)
{
    strcpy(dest, src);
    return 0;
}

void test_setAndGet()
{
    char *rootDir = "rootdir";
    MMKVMode mode = MMKV_SINGLE_PROCESS;
    char *cryptKey = "cryptKey";
    char *mmkvID = "test/Encrypt";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_SINGLE_PROCESS, nullptr, rootDir);

    setBool_c(mmkv, "bool", true);
    assert(getBool_c(mmkv, "bool", false));

    setInt32_t_c(mmkv, "int32", 1024);
    assert(getInt32_c(mmkv, "int32", 0) == 1024);

    setUint32_t_c(mmkv, "uint32", 2048);
    assert(getUInt32_c(mmkv, "uint32", 0) == 2048);

    setInt64_t_c(mmkv, "int64", 1024);
    assert(getInt64_c(mmkv, "int64", 0) == 1024);

    setUint64_t_c(mmkv, "uint64", 2048);
    assert(getUInt64_c(mmkv, "uint64", 0) == 2048);

    setFloat_c(mmkv, "float", 3.14f);
    assert(getFloat_c(mmkv, "float", 0) == 3.14f);

    setDouble_c(mmkv, "double", 3.1415926f);
    assert(getDouble_c(mmkv, "double", 0) == 3.1415926f);

    setString_c(mmkv, "string", "Hello, MMKV-示例 for CANGJIE");
    assert(strcmp(getString_c(mmkv, "string", nullptr), "Hello, MMKV-示例 for CANGJIE") == 0);

    byteArray byteArr;
    char *str = "Hello, MMKV";
    byteArr.data = (void *)str;
    byteArr.len = strlen(str);
    setBytes_c(mmkv, "Bytes", &byteArr);
    assert(getBytes_c(mmkv, "Bytes")->len == byteArr.len);

    DataList *list = (DataList *)malloc(sizeof(DataList) + getSizeofSize_t());
    list->size = 2;
    list->data[0].charArrayData = "Hello, CANGJIE";
    list->data[1].charArrayData = "Hello, cangjie";
    setStringSet_c(mmkv, "stringSet", list);
    assert(getStringSet_c(mmkv, "stringSet")->size == list->size);

    removeValueForKey_c(mmkv, "bool");
    assert(getBool_c(mmkv, "bool", false) == false);

    DataList *arrKeys = (DataList *)malloc(sizeof(DataList) + getSizeofSize_t());
    arrKeys->size = 2;

    arrKeys->data[0].charArrayData = "int";
    arrKeys->data[1].charArrayData = "long";
    removeValuesForKeys_c(mmkv, arrKeys);

    sync_c(mmkv, true);
    clearMemoryCache_c(mmkv);
    isFileValid_c(mmkvID, rootDir);
}

void test_keyIsNull()
{
    MMKVMode mode = MMKV_SINGLE_PROCESS;
    char *cryptKey = "cryptKey";

    MMKV *mmkv = defaultMMKV_c(mode, cryptKey);
    setBool_c(nullptr, "bool", true);
    assert(getBool_c(nullptr, "bool", false) == false);

    setInt32_t_c(nullptr, "int32", 1024);
    assert(getInt32_c(nullptr, "int32", 0) == 0);

    setUint32_t_c(nullptr, "uint32", 2048);
    assert(getUInt32_c(nullptr, "uint32", 0) == 0);

    setInt64_t_c(nullptr, "int64", 1024);
    assert(getInt64_c(nullptr, "int64", 0) == 0);

    setUint64_t_c(nullptr, "uint64", 2048);
    assert(getUInt64_c(nullptr, "uint64", 0) == 0);

    setFloat_c(nullptr, "float", 3.14f);
    assert(getFloat_c(nullptr, "float", 0) == 0);

    setDouble_c(nullptr, "double", 3.1415926f);
    assert(getDouble_c(nullptr, "double", 0) == 0);

    setString_c(nullptr, "string", "Hello, MMKV-示例 for CANGJIE");
    assert(getString_c(nullptr, "string", nullptr) == nullptr);

    setBytes_c(nullptr, "Bytes", nullptr);
    assert(getBytes_c(nullptr, "Bytes") == nullptr);

    setBytes_c(mmkv, "Bytes", nullptr);
    assert(getBytes_c(mmkv, "Bytes") == nullptr);

    setStringSet_c(nullptr, "stringSet", nullptr);
    assert(getStringSet_c(nullptr, "stringSet") == nullptr);

    setStringSet_c(mmkv, "stringSet", nullptr);
    assert(getStringSet_c(mmkv, "stringSet") == nullptr);
}

void testBackup()
{
    char *backDir = "rootdir_backup";
    char *rootDir = "rootdir";
    MMKVMode mode = MMKV_SINGLE_PROCESS;
    char *cryptKey = "cryptKey";
    char *mmkvID = "test/Encrypt";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_SINGLE_PROCESS, cryptKey, rootDir);
    getBool_c(mmkv, "bool", false);

    backupOne(mmkvID, backDir, rootDir);
    auto count = backupAll(backDir, rootDir);
}

void testRestore()
{
    char *backDir = "rootdir_backup";
    char *rootDir = "rootdir";
    MMKVMode mode = MMKV_SINGLE_PROCESS;
    char *cryptKey = "cryptKey";
    char *mmkvID = "test/Encrypt";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_SINGLE_PROCESS, cryptKey, nullptr);

    restoreOne(mmkvID, backDir, rootDir);
    auto count = restoreAll(backDir, rootDir);
}

void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const char *message)
{
    auto desc = [level] {
        switch (level) {
            case MMKVLogDebug:
                return "D";
            case MMKVLogInfo:
                return "I";
            case MMKVLogWarning:
                return "W";
            case MMKVLogError:
                return "E";
            default:
                return "N";
        }
    }();
    printf("redirecting-[%s] <%s:%d::%s> %s\n", desc, file, line, function, message);
}

void onMMKVContentChange(const char *mmapID)
{
    printf("MMKV other process has changed content of : %s\n", mmapID);
}

MMKVRecoverStrategic onMMKVCRCCheckFail(const char *mmapID)
{
    return OnErrorRecover;
}

MMKVRecoverStrategic onMMKVFileLengthError(const char *mmapID)
{
    return OnErrorRecover;
}

void test_setHandler(MMKVHandler handler)
{
    setCallbackHandler(handler);
}

void testCornetSize()
{
    char *aesKey = "aes";
    MMKV *mmkv = mmkvWithID_c("cornerSize", MMKV_SINGLE_PROCESS, aesKey, nullptr);
    auto size = pageSize_c() - 2;
    char *key = "key";
    char *value = (char *)malloc(size);
    setString_c(mmkv, key, value);
    trim_c(mmkv);
    free(value);
}

void testClose()
{
    char *mmkvID = "test/Encrypt";
    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_SINGLE_PROCESS, nullptr, nullptr);
    clearAll_c(mmkv);
    close_c(mmkv);
    onExit_c();
}

void process()
{
    char *rootDir = "rootdir";
    char *mmkvID = "process";
    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_MULTI_PROCESS, nullptr, nullptr);
    int i;
    for (i = 0; i < 10; i++) {
        string key = "int-" + to_string(i);
        lock_c(mmkv);
        setInt32_t_c(mmkv, (char *)key.c_str(), i);
        getInt32_c(mmkv, (char *)key.c_str(), 0);
        unlock_c(mmkv);
    }
}

void testProcess()
{
    pid_t pid;
    char *rootDir = "rootdir";
    char *mmkvID = "process";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_MULTI_PROCESS, nullptr, nullptr);
    pid = fork();
    if (pid <= 0) {
        process();
    } else {
        waitpid(pid, nullptr, 0);
        int i;

        checkContentChanged_c(mmkv);
        for (i = 0; i < 10; i++) {
            string key = "int-" + to_string(i);
            tryLock_c(mmkv);
            getInt32_c(mmkv, (char *)key.c_str(), 0);
            unlock_c(mmkv);
        }
    }
}

void testwriteValueToBuffer()
{
    pid_t pid;
    char *rootDir = "rootdir";
    char *mmkvID = "process";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_MULTI_PROCESS, nullptr, nullptr);
    setString_c(mmkv, "set00", "Hello, MMKV-示例 for CANGJIE");
    char* char_mmid = (char*)malloc(100);
    unsigned long Addr = (unsigned long)char_mmid;
    CPtrCoercion_c(Addr);
    writeValueToBuffer_c(mmkv, "set0", char_mmid, 50);
    free(char_mmid);
}

void testGetInformation00()
{
    pid_t pid;
    char *rootDir = "rootdir";
    char *mmkvID = "process";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_MULTI_PROCESS, nullptr, nullptr);
    setString_c(mmkv, "set00", "Hello, MMKV-示例 for CANGJIE");
    getValueSize_c(mmkv, "set0", true);
    containsKey_c(mmkv, "set0");
    count_c(mmkv);
    totalSize_c(mmkv);
    actualSize_c(mmkv);
    allKeys_c(mmkv);
    mmapID_c(mmkv);
    cryptKey_c(mmkv);
    reKey_c(mmkv, "keykeykey");
    checkReSetCryptKey_c(mmkv, "rekey");
}

void testGetInformation01()
{
    pid_t pid;
    char *rootDir = "rootdir";
    char *mmkvID = "process";

    MMKV *mmkv = mmkvWithID_c(mmkvID, MMKV_MULTI_PROCESS, nullptr, nullptr);
    setString_c(mmkv, "set00", "Hello, MMKV-示例 for CANGJIE");
    getValueSize_c(nullptr, "set0", true);
    containsKey_c(nullptr, "set0");
    count_c(nullptr);
    totalSize_c(nullptr);
    actualSize_c(nullptr);
    allKeys_c(nullptr);
    mmapID_c(nullptr);
    cryptKey_c(nullptr);
    reKey_c(nullptr, "keykeykey");
    reKey_c(mmkv, nullptr);

    checkReSetCryptKey_c(mmkv, nullptr);
}
void setup_mmkv_tests(void)
{
    char *rootDir = "rootdir";
    int logLevel = MMKVLogNone;
    MMKVHandler handler = {};
    handler.logHandler = nullptr;

    initializeMMKV_c(rootDir, logLevel, handler);
    test_keyIsNull();
    setLogLevel_c(MMKVLogInfo);
    handler.logHandler = myLogHandler;
    handler.onMMKVCRCCheckFail = onMMKVCRCCheckFail;
    handler.onMMKVFileLengthError = onMMKVFileLengthError;
    handler.onMMKVContentChange = onMMKVContentChange;
    test_setHandler(handler);
    test_setAndGet();
    testCornetSize();
    testwriteValueToBuffer();
    testGetInformation00();
    testGetInformation01();
    testProcess();
    handler = {};
    test_setHandler(handler);
    testBackup();
    testRestore();
    testClose();
}

#ifdef __cplusplus
}
#endif